//////////////////////////////////////
// Fast Container Library           //
// MIT License - Copyright (c) 2012 //
//////////////////////////////////////

#pragma once

#include "allocator.h"
#include "pool_allocator.h"

FC_NAMESPACE_BEGIN


// fixed_swap
//
// containers using unequal allocators have two problems with the normal swap function:
// a) infinite recursion of swap functions which would result in stack overflow, and
// b) instantiating an overly large object on the stack could result in stack overflow.
// 
// container.swap(x) functions use an inlined swap method to solve the former,
// however in the case of the latter problem it is up the user to resolve any issues
// by explicitely calling the fixed_swap alternative.
// 

template <class Container1, class Container2> inline
	void fixed_swap( Container1& a, Container2& b )
	{
		const size_t FC_MAX_STACK_USAGE = 16384;
		if( sizeof(Container1) < FC_MAX_STACK_USAGE )
		{
			Container1 temp(a);
			a = b;
			b = temp;
		}
		else
		{
			void* p = fc::allocate_temporary( sizeof(Container1), FC_ALIGNOF(Container1) );
			Container1* temp = new (p) Container1(a);
			a = b;
			b = *temp;
			temp->~Container1();
			fc::deallocate_temporary( p, sizeof(Container1) );
		}
	}


// fixed_allocator
//
// Allocator that stores a pointer to a user supplied buffer and can otherwise allocate
// and deallocate memory as a normal allocator with the exception that if deallocate
// is called with a pointer that is equal to the internal buffer pointer, no memory is
// freed. This allocator is used internally by fixed containers to prevent the possible
// freeing of stack-based memory or corruption of the heap.
//

class fixed_allocator
{
public:
	fixed_allocator() : _ptr_buffer(0) {}
	fixed_allocator( void* pBuffer ) : _ptr_buffer(pBuffer) {}

	void* allocate( size_t n, size_t alignment = 0 )
	{
		return fc::allocate( n, alignment );
	}

	void deallocate( void* ptr, size_t size = 0 )
	{
		// if an object deallocates an internal buffer then it can
		// be safely assumed that it is no longer in use.
		if( ptr == _ptr_buffer )
			_ptr_buffer = 0;

		else if( ptr )
			fc::deallocate( ptr, size );
	}

	void set_buffer( void* ptr ) { _ptr_buffer = ptr; }
	void* get_buffer() const { return _ptr_buffer; }

	fixed_allocator& operator =( const fixed_allocator& ) { return *this; }

	bool operator ==( const allocator& )
	{
		// if buffer is not in use then allocators are comparable.
		return !_ptr_buffer;
	}

	bool operator ==( const fixed_allocator& alloc )
	{
		// if both buffers are not in use then allocators are comparable.
		return !_ptr_buffer && !alloc._ptr_buffer;
	}

protected:
	void* _ptr_buffer;
};


// fixed_node_allocator
//
// allocator that can allocate fixed-size nodes from a user supplied memory pool 
// or fixed length buffer. If no such memory exists, or the fixed_node_allocator
// is full, then default_allocator is used to supply the memory.
//

class fixed_node_allocator : public pool_allocator
{
public:
	typedef pool_allocator			base_type;

	fixed_node_allocator()
		: base_type()
		{
		}

	fixed_node_allocator( void* ptrMemory, size_t nBytes, size_t objectSize, size_t alignment )
		: base_type(ptrMemory, nBytes, objectSize, alignment)
		{
		}

	void* allocate( size_t n, size_t alignment = 0 )
	{
		return can_allocate() ?
			base_type::allocate(n, alignment) :
			allocator::allocate(n, alignment);
	}

	void deallocate( void* ptr, size_t size = 0 )
	{
		// check for pool overflow. (used by fixed node containers)
		if( !owns_memory(ptr) )
			allocator::deallocate( ptr, size );

		base_type::deallocate( ptr, size );
	}

	fixed_node_allocator& operator =( const fixed_node_allocator& ) { return *this; }

	bool operator ==( const allocator& )
	{
		return false;
	}

};



FC_NAMESPACE_END

